【Unity】新手入门项目

您所在的位置:网站首页 愤怒的小鸟 图 【Unity】新手入门项目

【Unity】新手入门项目

2023-10-10 23:51| 来源: 网络整理| 查看: 265

前言

这个游戏算是本Unity菜鸡真正意义上从头到尾跟着教程,一步步踩坑到完成的游戏了。

写这篇博客主要是用于总结学习该游戏项目,嗯,下面开始吧。

(PS:这个游戏是跟着siki学院的愤怒的小鸟教程做的,b站地址:【SiKi学院Unity】Unity初级案例 - 愤怒的小鸟_哔哩哔哩_bilibili

官网的课程资料、源码及笔记下载地址:http:// http://www.sikiedu.com/course/134)

提示:以下是本篇文章正文内容,下面案例可供参考

一、游戏逻辑与设计

本款游戏作为一个入门级的Unity项目,它的实现逻辑并不复杂,首先把游戏场景分为三个:

加载界面关卡选择界面游戏界面其中,加载界面和关卡选择界面,可以选择Unity自带的UI功能进行实现,也就是用Image来显示背景图片和各个按键等组件布局;

关于游戏界面,例如小鸟、小猪、木块、背景和草地等对象,这里是通过新建一个空物体,然后添加图片的形式进行实现,然后例如暂停窗口、胜利窗口、失败窗口就可以使用UI进行实现,把所有窗口放在一个Canvas里,然后默认取消显示,当达成目标功能时(例如关卡胜利、失败,点击暂停按键等),就将它对应的组件设置为激活状态,这样就达成了界面显示的功能;

然后是关于游戏的逻辑了,由于愤怒的小鸟它主要的游戏核心,其实就是——碰撞。

所以在这里,绝大部分的游戏逻辑其实都是通过碰撞和触发进行实现,我们把小鸟、小猪、障碍物等组件加上刚体和碰撞器,然后根据需求针对特殊的个体组件添加触发器,然后我们可以通过判断碰撞和触发的状态,来决定是否发生了游戏对象的逻辑碰撞,然后选择执行对应代码。

二、游戏场景搭建

 

1.游戏背景

游戏背景就是由几个图片(背景图片、地面、草丛)拼接而成,并给地面添加一个碰撞器(BoxCollider2D),以便小鸟和敌方单位不会一直落下。

 ​

 

 

2.玩家模块

玩家模块由以下几个对象组成:

弹弓左部弹弓右部当前小鸟预备小鸟对于小鸟,需要添加其刚体与碰撞器组件,并根据不同小鸟的大小,调整碰撞范围;

关于小鸟的“弹弓模拟”操作,这里使用了Spring Joint2D组件进行实现,中心点为小鸟中心,左右两个点分别设置在左弹弓和右弹弓上的合适位置,然后可以根据情况调整距离和频率,这样就可以初步实现类似弹弓弹簧的效果(目前还无法飞出);

接下来关于拖拽小鸟,形成画线的功能,这里使用LineRender进行画线操作,在脚本中,当可以进行画线操作时(当前小鸟已经激活,但是还没有飞行),设置其每一段Line的两点并画线;

/// /// 划线 /// public void Line() { right.enabled = true; left.enabled = true; right.SetPosition(0, rightPos.position); right.SetPosition(1, this.transform.position); left.SetPosition(0, leftPos.position); left.SetPosition(1, this.transform.position); }

 

3.敌人模块

敌人模块就是靠自己进行发挥了,针对不同的物体对象(小猪、木块、木条、铁块、柱子...)设置其刚体和碰撞器(主要是碰撞体大小),然后设置其血量(最小和最大承受速度),受伤图片等等其它的变量,然后根据自己的想象和设计,设计出不同的关卡;

 ​​​

三、游戏代码模块 1.小鸟(包括特殊小鸟)

通过控制canMove来区分当前小鸟和等待小鸟的状态,当该小鸟为激活状态时,canMove为true,可以执行Line(弹弓画线)和Fly(飞行过程)方法,进行弹弓的操控与飞行操作,当飞行状态时,重新置canMove为false(防止重复操作),其中控制小鸟的操作由鼠标来执行,在代码中即是,在OnMouseUp、OnMouseDown来进行监听;

当飞行过程中,可以通过点击鼠标调用ShowSkill方法执行特殊小鸟的技能操作,这个方法可以写成虚方法,在后面的特殊小鸟中,进行重写并调用;当该小鸟飞出后,通过延时调用Next方法,重新销毁当前小鸟,并激活下一个等待小鸟;

using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; public class Bird : MonoBehaviour { public bool isClick = false; public float maxDis = 1.5f; [HideInInspector] public SpringJoint2D sp; protected Rigidbody2D rg; public LineRenderer right; public LineRenderer left; public Transform rightPos; public Transform leftPos; public GameObject boom; protected TestMyTrail myTrail; [HideInInspector] public bool canMove = false; public float amooth = 3; public AudioClip select; public AudioClip fly; private bool isFlay; public bool isReleased = false; public Sprite hurt; public SpriteRenderer render; public void Awake() { sp = GetComponent(); rg = GetComponent(); render = GetComponent(); myTrail = GetComponent(); } private void OnMouseDown() { if (canMove) { AudioPlay(select); isClick = true; rg.isKinematic = true; } } private void OnMouseUp() { if (canMove) { isClick = false; rg.isKinematic = false; Invoke("Fly", 0.1f); right.enabled = false; left.enabled = false; canMove = false; } } public void Update() { //如果点击的是UI界面,则直接返回 if (EventSystem.current.IsPointerOverGameObject()) { return; } if (isClick) { this.transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition); this.transform.position += new Vector3(0, 0, -Camera.main.transform.position.z); if(Vector3.Distance(this.transform.position,rightPos.position) > maxDis) { Vector3 pos = (this.transform.position - rightPos.position).normalized; pos *= maxDis; this.transform.position = pos + rightPos.position; } Line(); } //相机跟随 CamereMove(); //飞行时,点击左键 if (isFlay) { if (Input.GetMouseButtonDown(0)) { ShowSkill(); } } } public void CamereMove() { float posX = this.transform.position.x; Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, new Vector3(Mathf.Clamp(posX,0,17),Camera.main.transform.position.y, Camera.main.transform.position.z), amooth*Time.deltaTime); } public void Fly() { isReleased = true; isFlay = true; AudioPlay(fly); myTrail.StartTrail(); sp.enabled = false; Invoke("Next", 3); } /// /// 划线 /// public void Line() { right.enabled = true; left.enabled = true; right.SetPosition(0, rightPos.position); right.SetPosition(1, this.transform.position); left.SetPosition(0, leftPos.position); left.SetPosition(1, this.transform.position); } public virtual void Next() { Gamemanager._instance.birds.Remove(this); Destroy(this.gameObject); Instantiate(boom, this.transform.position, Quaternion.identity); Gamemanager._instance.NextBird(); } public void OnCollisionEnter2D(Collision2D collision) { isFlay = false; myTrail.ClearTrail(); } public void AudioPlay(AudioClip clip) { AudioSource.PlayClipAtPoint(clip,this.transform.position); } public virtual void ShowSkill() { isFlay = false; } public void Hurt() { render.sprite = hurt; } }

 

剩下的黄鸟(加速)、绿鸟(回旋)和黑鸟(爆炸),就是继承了redbird这个类,然后根据需求,重写ShowSkill方法即可;

public class YellowBird : Bird { public override void ShowSkill() { base.ShowSkill(); rg.velocity *= 2; } } public class GreenBird : Bird { public override void ShowSkill() { base.ShowSkill(); Vector3 speed = rg.velocity; speed.x *= -1; rg.velocity = speed; } }

 

其中,BlackBird因为要通过触发判断爆炸效果,并且爆炸后直接进行销毁,所以要额外多写几个方法进行实现

public class BlackBird : Bird { public List blocks = new List(); /// /// 进入触发区域 /// /// private void OnTriggerEnter2D(Collider2D collision) { if(collision.gameObject.tag == "Enemy") { blocks.Add(collision.gameObject.GetComponent()); } } /// /// 退出触发区域 /// /// private void OnTriggerExit2D(Collider2D collision) { if (collision.gameObject.tag == "Enemy") { blocks.Remove(collision.gameObject.GetComponent()); } } public override void ShowSkill() { base.ShowSkill(); if( blocks!=null && blocks.Count > 0) { for(int i = 0; i < blocks.Count; i++) { blocks[i].Dead(); } } OnClear(); } public void OnClear() { rg.velocity = Vector3.zero; Instantiate(boom, this.transform.position, Quaternion.identity); render.enabled = false; GetComponent().enabled = false; myTrail.ClearTrail(); } public override void Next() { Gamemanager._instance.birds.Remove(this); Destroy(this.gameObject); Gamemanager._instance.NextBird(); } }

 

 

2.敌人(包括小猪、木块等障碍物)

这里主要就是调整敌方的“血量”,但是这里的血量并不是一点一点扣除的,而是进行一个判断,当该物体碰撞的对象是Player(小鸟),并且当

碰撞速度>maxspeed时,敌方死亡minspeed= starts.Length) { break; } yield return new WaitForSeconds(0.2f); //Debug.Log(starts[i].name); starts[starsNum].SetActive(true); } } public void RePlay() { SaveData(); SceneManager.LoadScene(2); } public void Home() { SaveData(); SceneManager.LoadScene(1); } public void Next() { SaveData(); string currentLevel = PlayerPrefs.GetString("nowLevel"); Debug.Log(currentLevel); int num = int.Parse(currentLevel.Substring(5,1)) + 1; string nextLevel = currentLevel.Substring(0, currentLevel.Length - 1) + System.Convert.ToString(num); Debug.Log("下一关为: " + nextLevel); //加载下一关 PlayerPrefs.SetString("nowLevel", nextLevel); SceneManager.LoadScene(2); } public void SaveData() { Debug.Log("当前关卡的星星数量为: " + starsNum); //当前的星星数目大于已存储星星数目时,进行更新存储 if (starsNum > PlayerPrefs.GetInt(PlayerPrefs.GetString("nowLevel"))) { PlayerPrefs.SetInt(PlayerPrefs.GetString("nowLevel"), starsNum); } //存储所有的星星个数 int sum = 0; for(int i = 1; i = starsNum) { Debug.Log("星星总数为: " + PlayerPrefs.GetInt("totalNum")); isSelect = true; } if (isSelect) { locks.SetActive(false); starts.SetActive(true); //TODO:Text显示 TextShow(); } } public void TextShow() { int count = 0; for (int i = startNum; i 0时,激活该关卡,否则进行锁定;

当激活某一关时,要通过PlayerPrefs得到该关卡的星星数目,然后通过控制star[i](星星列表)进行星星的显示;

然后定义一个Slect方法,用于选择关卡,当点击某个关卡时,通过PlayerPrefs设置当前关卡为点击关卡,并通过SceneManager读取场景

public class LevelSelect : MonoBehaviour { public bool isSelect = false; public Sprite levelBG; public Image img; public GameObject[] stars; public void Awake() { img = GetComponent(); } public void Start() { //是第一关 if(this.transform.name == this.transform.parent.GetChild(0).name) { isSelect = true; } else { int beforeNum = int.Parse(this.gameObject.name) - 1; if( PlayerPrefs.GetInt("level"+beforeNum.ToString()) > 0) { isSelect = true; } } //激活关卡 if (isSelect) { img.overrideSprite = levelBG; this.transform.Find("num").gameObject.SetActive(true); //读取星星个数 int count = PlayerPrefs.GetInt("level" + this.gameObject.name); if (count > 0) { for(int i = 0; i < count; i++) { stars[i].SetActive(true); } } } } public void Selected() { if (isSelect) { PlayerPrefs.SetString("nowLevel", "level" + this.gameObject.name); SceneManager.LoadScene(2); //Debug.Log("选择成功"); } } }

 

6.暂停界面

通过UI设计一个暂停界面,当为激活时,它在游戏窗口外,当激活时,通过动画,将UI界面移入游戏窗口;

 ​​​

定义方法Pause,当点击暂停按钮时,调用该方法,然后调用暂停动画,将UI窗口移入游戏界面,

设置Time.timeScale = 0,以达到暂停游戏的效果,并隐藏该暂停按钮;

然后定义Resume方法,当点击还原按钮时,调用该方法,调用还原动画,重新将UI窗口移除游戏界面,并设置第一个小鸟为激活状态,并显示暂停按钮;

接下来定义Home方法和Retry方法,分别实现返回首页,和重新开始该关卡的操作,这里可以调用Gamemaneger中的Home和Retry方法,不过要注意提前设置Time.timeScale = 1,以免游戏继续暂停;

public class PausePanel : MonoBehaviour { private Animator anim; public GameObject button; public void Awake() { anim = GetComponent(); } /// /// Home按键 /// public void Home() { Time.timeScale = 1; Gamemanager._instance.Home(); } /// /// Retry按键 /// public void Retry() { Time.timeScale = 1; Gamemanager._instance.RePlay(); } /// /// Pause按键 /// public void Pause() { anim.SetBool("isPause", true); button.SetActive(false); //暂停 if (Gamemanager._instance.birds.Count > 0) { if(Gamemanager._instance.birds[0].isReleased == false) { Gamemanager._instance.birds[0].canMove = false; } } } /// /// Resume按键 /// public void Resume() { Time.timeScale = 1; anim.SetBool("isPause", false); //还原 if (Gamemanager._instance.birds.Count > 0) { if (Gamemanager._instance.birds[0].isReleased == false) { Gamemanager._instance.birds[0].canMove = true; } } } /// /// pause动画结束后调用 /// public void PauseAnimEnd() { Time.timeScale = 0; } /// /// resume动画结束后调用 /// public void ResumeAnimEnd() { button.SetActive(true); } }

 

 

四、场景间的组合搭配

一个游戏场景由以下几个模块构成:

Main Camera 主摄像机Player 玩家模块,包括弹弓、当前小鸟、准备小鸟Enemy 敌人模块,包括所有敌方单位:小猪、木块等障碍物env 游戏背景:背景图片,地面,草丛Cavas UI画布,所有的UI组件都放在这里,用于管理UI的所有操作UICamera UI摄像机,查看UI镜头Gamemenager 游戏管理器,挂载Gamemaneger脚本,管理几乎所有的游戏逻辑与功能模块

五、游戏的发布

关于游戏的发布,首先在项目设置里,设置你的游戏画面、游戏图标、鼠标图标等设置

 ​​​

然后进入Build Settings,设置需要发布的场景画面Scenes,根据需求,在不同的平台上发布游戏,在这里我选择发布的平台是Windows和Android(安卓发布,需要设置例如jdk、sdk,ndk等环境配置)

 ​​​

最后点击Build,铛铛,大功告成~

 

总结

以上就是一个Unity入门菜鸡开发的入门级2D游戏项目,本文主要用于自己学习总结,如有不对的地方望各位大佬指正。

下面是已发布成功的游戏本体:

PC和安卓:https://pan.baidu.com/s/1Q6SH-YWx7u4qF5DFDjOVqw 提取码:9xpx

安卓(蓝奏云):https://www.lanzouw.com/b02oe9adi 密码:2zp4



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3